package com.aplink.generic.google.maps.clustering.algo;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import android.support.v4.util.LongSparseArray;

import com.aplink.generic.google.maps.clustering.Cluster;
import com.aplink.generic.google.maps.clustering.ClusterItem;
import com.aplink.generic.google.maps.geometry.Point;
import com.aplink.generic.google.maps.projection.SphericalMercatorProjection;

/**
 * Groups markers into a grid.
 */
public class GridBasedAlgorithm<T extends ClusterItem> implements Algorithm<T> {
	private static final int GRID_SIZE = 100;

	private static long getCoord(final long numCells, final double x,
			final double y) {
		return (long) ((numCells * Math.floor(x)) + Math.floor(y));
	}

	private final Set<T> mItems = Collections.synchronizedSet(new HashSet<T>());

	@Override
	public void addItem(final T item) {
		mItems.add(item);
	}

	@Override
	public void addItems(final Collection<T> items) {
		mItems.addAll(items);
	}

	@Override
	public void clearItems() {
		mItems.clear();
	}

	@Override
	public Set<? extends Cluster<T>> getClusters(final double zoom) {
		final long numCells = (long) Math.ceil((256 * Math.pow(2, zoom))
				/ GridBasedAlgorithm.GRID_SIZE);
		final SphericalMercatorProjection proj = new SphericalMercatorProjection(
				numCells);

		final HashSet<Cluster<T>> clusters = new HashSet<Cluster<T>>();
		final LongSparseArray<StaticCluster<T>> sparseArray = new LongSparseArray<StaticCluster<T>>();

		synchronized (mItems) {
			for (final T item : mItems) {
				final Point p = proj.toPoint(item.getPosition());

				final long coord = GridBasedAlgorithm.getCoord(numCells, p.x,
						p.y);

				StaticCluster<T> cluster = sparseArray.get(coord);
				if (cluster == null) {
					cluster = new StaticCluster<T>(proj.toLatLng(new Point(Math
							.floor(p.x) + .5, Math.floor(p.y) + .5)));
					sparseArray.put(coord, cluster);
					clusters.add(cluster);
				}
				cluster.add(item);
			}
		}

		return clusters;
	}

	@Override
	public Collection<T> getItems() {
		return mItems;
	}

	@Override
	public void removeItem(final T item) {
		mItems.remove(item);
	}
}
